import os
import json
import random
import time
from playwright.sync_api import sync_playwright
from pathlib import Path
from datetime import datetime

# 配置参数
CONFIG = {
    "json_path": "./cell_data/cell_articles.json", # 下载的json文件路径
    "download_dir": "./cell_papers_downloaded",  # 下载的文件保存路径
    "headless": False,  
    "viewport": {"width": 1920, "height": 1080},
    "button_pos": {"x": 1814, "y": 30}, # 下载按钮位置
    "delays": {
        "page_load": (3, 5),
        "pre_click": (0.5, 1),
        "post_click": (5, 12)
    },
    "retry": {
        "max_attempts": 1,  # 增加重试次数
        "backoff": [5, 10, 15]
    },
    "timeouts": {
        "download": 60000,  # 将下载超时增加到60秒
        "screenshot": 10000  # 截图超时10秒
    },
    # 需要跳过那些状态
    # downloaded - 已下载
    # skipped - 跳过
    # failed - 失败
    "skip_states": ["downloaded","skipped","failed"]
}

def sanitize_filename(title):
    """生成安全的文件名"""
    return "".join(c for c in title if c.isalnum() or c in (' ', '-', '_')).strip() + ".pdf"

def get_existing_files(download_dir):
    """获取已存在的文件列表"""
    return set(os.listdir(download_dir)) if os.path.exists(download_dir) else set()

def simulate_human_click(page, x, y):
    """模拟人类点击行为"""
    for _ in range(random.randint(2, 4)):
        page.mouse.move(
            x + random.randint(-20, 20),
            y + random.randint(-20, 20),
            steps=random.randint(3, 5) # 添加随机移动步数
        )
        time.sleep(random.uniform(0.1, 0.3))
    page.mouse.click(x, y, delay=random.uniform(100, 300))

def update_json_status(json_path, article_title, status):
    """更新JSON文件中文章的下载状态"""
    try:
        with open(json_path, 'r', encoding='utf-8') as f:
            articles = json.load(f)
        
        for article in articles:
            if article['title'] == article_title:
                article['download_status'] = status
                break
        
        with open(json_path, 'w', encoding='utf-8') as f:
            json.dump(articles, f, indent=2, ensure_ascii=False)
    except Exception as e:
        print(f"❌ 更新JSON状态失败: {str(e)}")

def handle_human_verification(page):
    """处理人机验证页面"""
    # 检查是否出现人机验证
    if "继续之前，" in page.content():
        print("🛡️ 检测到人机验证，尝试自动处理...")
        
        # 随机延迟，模拟人类阅读时间
        time.sleep(random.uniform(10, 15))
        
        # 模拟人类鼠标移动轨迹
        start_x = random.randint(300, 500)
        start_y = random.randint(200, 400)
        
        # 创建随机移动路径
        for step in range(random.randint(3, 6)):
            target_x = start_x + random.randint(-50, 50)
            target_y = start_y + random.randint(-50, 50)
            
            # 随机移动步数和速度
            page.mouse.move(
                target_x,
                target_y,
                steps=random.randint(5, 10),
                delay=random.randint(50, 200)
            )
            time.sleep(random.uniform(0.1, 0.5))
        
        # 最终点击位置（在目标区域附近随机偏移）
        click_x = 532 + random.randint(-5, 5)
        click_y = 292 + random.randint(-5, 5)
        
        print(f"🖱️ 模拟人类点击验证按钮 ({click_x}, {click_y})")
        page.mouse.click(click_x, click_y, delay=random.randint(100, 300))
        
        # 等待验证结果
        time.sleep(random.uniform(3, 6))
        
        # 检查是否验证成功
        if "继续之前，" in page.content():
            print("❌ 自动验证失败，可能需要手动处理")
            return False
        print("✅ 验证通过")
        return True
    return False

def download_article(context, article, download_dir):
    """处理单篇文章下载"""
    title = article['title']
    pdf_url = article['pdf_link']
    filename = sanitize_filename(title)
    save_path = os.path.join(download_dir, filename)
    
    print(f"\n📄 正在处理: {title[:50]}...")
    
    try:
        # 创建新页面
        page = context.new_page()
        
        # 访问页面
        page.goto(pdf_url, timeout=15000)
        time.sleep(random.uniform(*CONFIG['delays']['page_load']))
        
        # # 处理人机验证
        # if not handle_human_verification(page):
        #     print("⚠️ 人机验证处理失败，跳过下载")
        #     update_json_status(CONFIG['json_path'], title, "verification_failed")
        #     return "verification_failed"
        
        # 检查是否需要跳过
        if page.query_selector('.header'):
            print(f"⚠️ 非公开下载内容 跳过: {title[:50]}...")
            update_json_status(CONFIG['json_path'], title, "skipped")
            return "skipped"
        
        # 检查权限
        if "Access Denied" in page.title():
            raise PermissionError("访问被拒绝，请检查订阅状态")
        
        # 点击下载按钮
        print(f"🖱️ 正在点击位置 ({CONFIG['button_pos']['x']}, {CONFIG['button_pos']['y']})")
        simulate_human_click(page, CONFIG['button_pos']['x'], CONFIG['button_pos']['y'])
        time.sleep(random.uniform(*CONFIG['delays']['post_click']))
        
        # 处理下载 - 增加超时时间
        try:
            with page.expect_download(timeout=CONFIG['timeouts']['download']) as download_info:
                pass
            
            download = download_info.value
            download.save_as(save_path)
            
            print(f"✅ 成功下载: {filename}")
            update_json_status(CONFIG['json_path'], title, "downloaded")
            return "downloaded"
        except Exception as download_error:
            print(f"❌ 下载等待失败: {str(download_error)}")
            update_json_status(CONFIG['json_path'], title, "failed")
            return "failed"
        
    except Exception as e:
        print(f"❌ 下载失败: {str(e)},失败的链接地址: {pdf_url}")
        if 'page' in locals():
            page.screenshot(path=os.path.join(download_dir, f"error_{int(time.time())}.png"))
        return "failed"
    finally:
        if 'page' in locals():
            page.close()

def batch_download():
    """执行批量下载"""
    start_time = datetime.now()
    # 初始化环境
    os.makedirs(CONFIG['download_dir'], exist_ok=True)
    existing_files = get_existing_files(CONFIG['download_dir'])
    print(f"📂 下载目录: {os.path.abspath(CONFIG['download_dir'])}")
    print(f"🔍 已存在 {len(existing_files)} 个文件")
    
    with open(CONFIG['json_path'], 'r', encoding='utf-8') as f:
        articles = json.load(f)
    
    # 过滤已存在的文件
    filtered_articles = [
        art for art in articles 
        if sanitize_filename(art['title']) not in existing_files and 
           art.get('download_status') not in CONFIG['skip_states']
    ]
    print(f"📊 共 {len(articles)} 篇文章，{len(filtered_articles)} 篇需要下载")
    
    if not filtered_articles:
        print("🎉 所有文件已存在或已标记，无需下载")
        return
    
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=CONFIG['headless'])
        context = browser.new_context(
            accept_downloads=True,
            viewport=CONFIG['viewport']
        )
        
        try:
            stats = {
                'downloaded': 0,
                'skipped': 0,
                'failed': 0
            }
            
            for idx, article in enumerate(filtered_articles, 1):
                print(f"\n🔍 进度: {idx}/{len(filtered_articles)}")
                
                for attempt in range(CONFIG['retry']['max_attempts']):
                    result = download_article(context, article, CONFIG['download_dir'])
                    if result in ['downloaded', 'skipped']:
                        stats[result] += 1
                        break
                    elif attempt < CONFIG['retry']['max_attempts'] - 1:
                        wait = CONFIG['retry']['backoff'][attempt]
                        print(f"⏳ 第 {attempt+1} 次重试，等待 {wait} 秒...")
                        time.sleep(wait)
                        # 每次重试前关闭所有页面
                        for page in context.pages:
                            page.close()
                    else:
                        stats['failed'] += 1
                
                # 随机间隔
                if idx < len(filtered_articles):
                    interval = random.uniform(60, 120)
                    print(f"💤 随机等待 {interval:.1f} 秒...")
                    time.sleep(interval)
                    
        finally:
            context.close()
            browser.close()
            # 记录结束时间并计算耗时
    end_time = datetime.now()
    total_time = end_time - start_time
    print(f"📅 开始时间: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
    print(f"\n脚本结束时间: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
    print(f"总耗时: {total_time}")
    print(f"\n📊 下载统计:")
    print(f"✅ 成功下载: {stats['downloaded']}")
    print(f"⚠️ 跳过下载: {stats['skipped']}")
    print(f"❌ 下载失败: {stats['failed']}")
    print(f"总文件数: {len(existing_files) + stats['downloaded']}")
    print(f"文件保存在: {os.path.abspath(CONFIG['download_dir'])}")

if __name__ == "__main__":
    batch_download()